// SUGARSCAPE MODEL FOR SWARM
// Based on Nelson Minar's 1996-1997 model; modified by 
// Andre Manoel (andremanoel@gmail.com) with research  
// purposes. See file README for more information.

#import "ObserverSwarm.h"
#import <gui.h> // Pixmap

@implementation ObserverSwarm

// Set up configurable display parameters
+ createBegin: aZone
{
  ObserverSwarm *obj;
  id <ProbeMap> probeMap;
  
  obj = [super createBegin: aZone];

  obj->displayFrequency = 1;
  obj->drawPopulationGraph = 1;
  obj->drawAttributeGraph = 1;
  obj->drawTagGraph = 0;
  obj->drawPriceGraph = 0;
  obj->drawTradesGraph = 0;
  obj->drawPriceStdDevGraph = 0;
  obj->drawQuantGraph = 0;
  obj->drawForesightGraph = 0;
  obj->drawSicknessGraph = 0;
  obj->drawSugarHistogram = 1;
  obj->drawSpiceHistogram = 0;
  obj->drawAgeHistogram = 0;
  obj->drawMrsHistogram = 0;
  obj->parameterFile = NULL;

  probeMap = [EmptyProbeMap createBegin: aZone];
  [probeMap setProbedClass: [self class]];
  probeMap = [probeMap createEnd];

  [probeMap addProbe: [probeLibrary getProbeForMessage: "setParameterFile:"
 				    inClass: [self class]]];
  [probeMap addProbe: [probeLibrary getProbeForMessage: "saveParameters:"
				    inClass: [self class]]];
  [probeMap addProbe: [probeLibrary getProbeForVariable: "drawPopulationGraph"
				    inClass: [self class]]];
  [probeMap addProbe: [probeLibrary getProbeForVariable: "drawAttributeGraph"
				    inClass: [self class]]];				    
  [probeMap addProbe: [probeLibrary getProbeForVariable: "drawTagGraph"
				    inClass: [self class]]];
  [probeMap addProbe: [probeLibrary getProbeForVariable: "drawPriceGraph"
				    inClass: [self class]]];
  [probeMap addProbe: [probeLibrary getProbeForVariable: "drawTradesGraph"
				    inClass: [self class]]];
  [probeMap addProbe: [probeLibrary getProbeForVariable: "drawPriceStdDevGraph"
				    inClass: [self class]]];
  [probeMap addProbe: [probeLibrary getProbeForVariable: "drawQuantGraph"
				    inClass: [self class]]];
  [probeMap addProbe: [probeLibrary getProbeForVariable: "drawForesightGraph"
				    inClass: [self class]]];
  [probeMap addProbe: [probeLibrary getProbeForVariable: "drawSicknessGraph"
				    inClass: [self class]]];
  [probeMap addProbe: [probeLibrary getProbeForVariable: "drawSugarHistogram"
				    inClass: [self class]]];
  [probeMap addProbe: [probeLibrary getProbeForVariable: "drawSpiceHistogram"
				    inClass: [self class]]];
  [probeMap addProbe: [probeLibrary getProbeForVariable: "drawMrsHistogram"
				    inClass: [self class]]];
  [probeMap addProbe: [probeLibrary getProbeForVariable: "drawAgeHistogram"
				    inClass: [self class]]];
  [probeMap addProbe: [probeLibrary getProbeForVariable: "displayFrequency"
				    inClass: [self class]]];

  [probeLibrary setProbeMap: probeMap For: [self class]];

  return obj;
}

- _sugarHistogramDeath_: caller
{
  [sugarHistogram drop];
  sugarHistogram = nil;
  return self;
}

- _spiceHistogramDeath_: caller
{
  [spiceHistogram drop];
  spiceHistogram = nil;
  return self;
}

- _mrsHistogramDeath_: caller
{
  [mrsHistogram drop];
  mrsHistogram = nil;
  return self;
}

- _ageHistogramDeath_: caller
{
  [ageHistogram drop];
  ageHistogram = nil;
  return self;
}

- _worldRasterDeath_: caller
{
  [worldRaster drop];
  worldRaster = nil;
  return self;
}

// Create all the objects
- buildObjects
{
  int i;
  int maxSugarValue, maxSpiceValue;
  SugarSpace *sugarSpace;
  id agentList;

  [super buildObjects];
  
  // Show a probe map so that users can type in a parameter
  //   file name if they want. It is ARCHIVED, meaning it will
  //   save the window positions if you hit the "save" button
  CREATE_ARCHIVED_PROBE_DISPLAY (self);

  // Set our state to stopped - this code also waits until the user
  //   clicks "go" so he has a chance to change parameters
  [controlPanel setStateStopped];

  // If a parameter file was entered, use it
  if (parameterFile != NULL) {
    id archiver = [LispArchiver create: self setPath: parameterFile];
    if ((modelSwarm = [archiver getWithZone: self key: "model"]) == nil)
      raiseEvent(InvalidOperation, 
                 "Can't find the parameters to create modelSwarm");
    [archiver drop];
  }
  else modelSwarm = [ModelSwarm create: self];
    
  // Now create probes for the Swarms so the user can retool settings
  CREATE_ARCHIVED_PROBE_DISPLAY (modelSwarm);
  
  // Stop the control panel again in case users want to revise
  //   the settings
  [controlPanel setStateStopped];

  // Now tell the model swarm to build its objects
  [modelSwarm buildObjects];

  // Read some handy objects out of the model swarm
  sugarSpace = [modelSwarm getSugarSpace];
  agentList = [modelSwarm getAgentList];

  // Compute the colourmap
  maxSugarValue = [[modelSwarm getSugarSpace] getGlobalMaxSugar];
  maxSpiceValue = [[modelSwarm getSugarSpace] getGlobalMaxSpice];

  colormap = [Colormap create: self];
  // Colours for the sugar
  for (i = 0; i < maxSugarValue; i++)
    [colormap setColor: i
  	      ToRed: (double) i / (maxSugarValue - 1.0)
  	      Green: (double) i / (maxSugarValue - 1.0)
  	      Blue: 0];

  // Coulours for the spice
  for (i = 50; i < 50 + maxSpiceValue; i++)
    [colormap setColor: i ToGrey: (double) ((i-50) / (maxSpiceValue -1.0))];
 
  // Colours for the agents
  [colormap setColor: 100 ToName: "red"];	  // agent colour
  [colormap setColor: 200 ToName: "blue"];
  [colormap setColor: 150 ToName: "green"];

  // Next, create a 2D window for displaying the world
  worldRaster = [ZoomRaster createBegin: self];
  SET_WINDOW_GEOMETRY_RECORD_NAME (worldRaster);
  worldRaster = [worldRaster createEnd];
  [worldRaster enableDestroyNotification: self
               notificationMethod: @selector (_worldRasterDeath_:)];
  [worldRaster setColormap: colormap];
  [worldRaster setZoomFactor: 6];
  [worldRaster setWidth: [[sugarSpace getAgentGrid] getSizeX]
               Height: [[sugarSpace getAgentGrid] getSizeY]];
  [worldRaster setWindowTitle: "SugarScape"];
  [worldRaster pack];                             // draw the window

  // Create an object to display the sugar and spice values
  sugarDisplay = [Value2dDisplay createBegin: self];
  [sugarDisplay setDisplayWidget: worldRaster colormap: colormap];
  [sugarDisplay setDiscrete2dToDisplay: 
    [[modelSwarm getSugarSpace] getValues]];
  // [sugarDisplay setDisplayMappingM: 1 C: 0];	  // map to colourmap
  sugarDisplay = [sugarDisplay createEnd];

  // And an object to display the agents
  agentDisplay = [Object2dDisplay createBegin: self];
  [agentDisplay setDisplayWidget: worldRaster];
  [agentDisplay setDiscrete2dToDisplay: [sugarSpace getAgentGrid]];
  [agentDisplay setObjectCollection: [modelSwarm getAgentList]];
  [agentDisplay setDisplayMessage: M(drawSelfOn:)];   // draw method
  agentDisplay = [agentDisplay createEnd];

  // Enable probes on the world
  [worldRaster setButton: ButtonRight
               Client: agentDisplay
               Message: M(makeProbeAtX:Y:)];

  if (drawPopulationGraph)
    {
      // And create a graph of population in the world
      populationGraph = [EZGraph createBegin: self];
      SET_WINDOW_GEOMETRY_RECORD_NAME (populationGraph);
      [populationGraph setTitle: "Population over time"];
      [populationGraph setAxisLabelsX: "time" Y: "population"];
      populationGraph = [populationGraph createEnd];
      
      // One data sequence in the graph - total population
      [populationGraph createSequence: "population"
                       withFeedFrom: agentList
                       andSelector: M(getCount)];                      
    }
  
  // Create a graph for various agent attributes
  if (drawAttributeGraph) 
    {
      attributeGraph = [EZGraph createBegin: self];
      SET_WINDOW_GEOMETRY_RECORD_NAME (attributeGraph);
      [attributeGraph setTitle: "Agent attributes over time"];
      [attributeGraph setAxisLabelsX: "time" Y: "attribute"];
      attributeGraph = [attributeGraph createEnd];
  
      // Three data sequences here. Average vision for all the bugs
      [attributeGraph createAverageSequence: "vision"
	  	      withFeedFrom: agentList
		      andSelector: M(getVision)];
      // And average sugar and spice metabolism
      [attributeGraph createAverageSequence: "sugar metabolism"
		      withFeedFrom: agentList
		      andSelector: M(getSugarMetabolism)];
      [attributeGraph createAverageSequence: "spice metabolism"
		      withFeedFrom: agentList
		      andSelector: M(getSpiceMetabolism)];
    }
		      
  if (drawTagGraph)
    {
      // Create a graph of tag time series
      tagGraph = [EZGraph createBegin: self];
      SET_WINDOW_GEOMETRY_RECORD_NAME (tagGraph);
      [tagGraph setTitle: "Blue tribe over time"];
      [tagGraph setAxisLabelsX: "time" Y: "fraction"];
      tagGraph = [tagGraph createEnd];
      
      // One data sequence in the graph - fraction blue
      [tagGraph createAverageSequence: "blue"
                withFeedFrom: agentList
                andSelector: M(getTribe)];
    }

  if (drawPriceGraph)
    {
      // Create a graph of avg. price series
      priceGraph = [EZGraph createBegin: self];
      SET_WINDOW_GEOMETRY_RECORD_NAME (priceGraph);
      [priceGraph setTitle: "Average price over time"];
      [priceGraph setAxisLabelsX: "time" Y: "avg. price"];
      //[priceGraph setFileOutput: 1];
      priceGraph = [priceGraph createEnd];
            
      // One data sequence in the graph - avg. price
      [priceGraph createSequence: "avg. price"
                withFeedFrom: modelSwarm
                andSelector: M(getAvgPrice)];
    }
  
  if (drawTradesGraph)
    {
      // Create a graph of trade volume series
      tradesGraph = [EZGraph createBegin: self];
      SET_WINDOW_GEOMETRY_RECORD_NAME (tradesGraph);
      [tradesGraph setTitle: "Trade volume over time"];
      [tradesGraph setAxisLabelsX: "time" Y: "trade volume"];
      //[tradesGraph setFileOutput: 1];
      tradesGraph = [tradesGraph createEnd];
      
      // One data sequence in the graph - trade volume
      [tradesGraph createTotalSequence: "trade volume"
                withFeedFrom: agentList
                andSelector: M(getVolume)];
    }
  
  if (drawQuantGraph)
    {
      // Create a graph of total sugar and spice quantities
      quantGraph = [EZGraph createBegin: self];
      SET_WINDOW_GEOMETRY_RECORD_NAME (quantGraph);
      [quantGraph setTitle: "Total quantities over time"];
      [quantGraph setAxisLabelsX: "time" Y: "quantity"];
      //[quantGraph setFileOutput: 1];
      quantGraph = [quantGraph createEnd];
      
      // Two data sequences in the graph - spice and sugar quantities
      [quantGraph createAverageSequence: "sugar quantity"
                withFeedFrom: agentList
                andSelector: M(getCurrentSugar)];

      [quantGraph createAverageSequence: "spice quantity"
                withFeedFrom: agentList
                andSelector: M(getCurrentSpice)];
    }
  
  if (drawForesightGraph)
    {
      // Create a graph of average foresight
      foresightGraph = [EZGraph createBegin: self];
      SET_WINDOW_GEOMETRY_RECORD_NAME (foresightGraph);
      [foresightGraph setTitle: "Average foresight"];
      [foresightGraph setAxisLabelsX: "time" Y: "avg. foresight"];
      //[foresightGraph setFileOutput: 1];
      foresightGraph = [foresightGraph createEnd];
      
      // One data sequence in the graph - avg. foresight
      [foresightGraph createAverageSequence: "avg. foresight"
                withFeedFrom: agentList
                andSelector: M(getForesight)];
    }
  
  if (drawSicknessGraph)
    {
      // Create a graph of no. of sick people
      sicknessGraph = [EZGraph createBegin: self];
      SET_WINDOW_GEOMETRY_RECORD_NAME (sicknessGraph);
      [sicknessGraph setTitle: "Average sickness"];
      [sicknessGraph setAxisLabelsX: "time" Y: "fraction of sick people"];
      //[sicknessGraph setFileOutput: 1];
      sicknessGraph = [sicknessGraph createEnd];
      
      // One data sequence in the graph - avg. sickness
      [sicknessGraph createAverageSequence: "sickness"
                withFeedFrom: agentList
                andSelector: M(getSickness)];
    }
  
  if (drawPriceStdDevGraph)
    {
      // Create a graph of std. dev. in mean prices log series
      priceStdDevGraph = [EZGraph createBegin: self];
      SET_WINDOW_GEOMETRY_RECORD_NAME (priceStdDevGraph);
      [priceStdDevGraph setTitle: "Std. dev. in mean prices log per time"];
      [priceStdDevGraph setAxisLabelsX: "time" Y: "price std. dev."];
      //[priceStdDevGraph setFileOutput: 1];
      priceStdDevGraph = [priceStdDevGraph createEnd];
      
      // One data sequence in the graph - price std. dev.
      [priceStdDevGraph createSequence: "price std. dev."
                withFeedFrom: modelSwarm
                andSelector: M(getAvgPriceLogStdDev)];
    }
  
  if (drawSugarHistogram)
    {
      // Create a histogram of agent sugar distribution
      sugarHistogram = [EZBin createBegin: self];
      SET_WINDOW_GEOMETRY_RECORD_NAME (sugarHistogram);
      [sugarHistogram setTitle: "Agent sugar distribution"];
      [sugarHistogram setAxisLabelsX: "sugar" Y: "number of agents"];
      [sugarHistogram setBinCount: 9];
      [sugarHistogram setLowerBound: 0];
      [sugarHistogram setUpperBound: 300];
      [sugarHistogram setCollection: agentList];
      [sugarHistogram setProbedSelector: M(getCurrentSugar)];
      sugarHistogram = [sugarHistogram createEnd];
      [sugarHistogram enableDestroyNotification: self
                       notificationMethod: @selector(_sugarHistogramDeath_:)];
    }
  
  if (drawSpiceHistogram)
    {
      // Create a histogram of agent spice distribution
      spiceHistogram = [EZBin createBegin: self];
      SET_WINDOW_GEOMETRY_RECORD_NAME (spiceHistogram);
      [spiceHistogram setTitle: "Agent spice distribution"];
      [spiceHistogram setAxisLabelsX: "spice" Y: "number of agents"];
      [spiceHistogram setBinCount: 9];
      [spiceHistogram setLowerBound: 0];
      [spiceHistogram setUpperBound: 300];
      [spiceHistogram setCollection: agentList];
      [spiceHistogram setProbedSelector: M(getCurrentSpice)];
      spiceHistogram = [spiceHistogram createEnd];
      [spiceHistogram enableDestroyNotification: self
                       notificationMethod: @selector(_spiceHistogramDeath_:)];
    }
  
  if (drawMrsHistogram)
    {
      // Create a histogram of agent MRS distribution
      mrsHistogram = [EZBin createBegin: self];
      SET_WINDOW_GEOMETRY_RECORD_NAME (mrsHistogram);
      [mrsHistogram setTitle: "Agent MRS distribution"];
      [mrsHistogram setAxisLabelsX: "MRS" Y: "number of agents"];
      [mrsHistogram setBinCount: 21];
      [mrsHistogram setLowerBound: 0.5];
      [mrsHistogram setUpperBound: 1.5];
      [mrsHistogram setCollection: agentList];
      [mrsHistogram setProbedSelector: M(getMrs)];
      mrsHistogram = [mrsHistogram createEnd];
      [mrsHistogram enableDestroyNotification: self
                       notificationMethod: @selector(_mrsHistogramDeath_:)];
    }
  
  if (drawAgeHistogram)
    {
      // Create a histogram of agent age distribution
      ageHistogram = [EZBin createBegin: self];
      SET_WINDOW_GEOMETRY_RECORD_NAME (ageHistogram);
      [ageHistogram setTitle: "Agent age distribution"];
      [ageHistogram setAxisLabelsX: "age" Y: "number of agents"];
      [ageHistogram setBinCount: 10];
      [ageHistogram setLowerBound: 0];
      [ageHistogram setUpperBound: 100];
      [ageHistogram setCollection: agentList];
      [ageHistogram setProbedSelector: M(getAge)];
      ageHistogram = [ageHistogram createEnd];
      [ageHistogram enableDestroyNotification: self
                    notificationMethod: @selector(_ageHistogramDeath_:)];
    }
    
  return self;
}

- _updateSugarHistogram_
{
  if (sugarHistogram)
    {
      [sugarHistogram reset];
      [sugarHistogram update];
      [sugarHistogram output];
    }
  return self; 
}

- _updateSpiceHistogram_
{
  if (spiceHistogram)
    {
      [spiceHistogram reset];
      [spiceHistogram update];
      [spiceHistogram output];
    }
  return self; 
}

- _updateMrsHistogram_
{
  if (mrsHistogram)
    {
      [mrsHistogram reset];
      [mrsHistogram update];
      [mrsHistogram output];
    }
  return self; 
}

- _updateAgeHistogram_
{  

  if (ageHistogram)
    {
      [ageHistogram reset];
      [ageHistogram update];
      [ageHistogram output];
    }
  return self;
}

- _updateDisplay_
{
  if (worldRaster)
    {
      [sugarDisplay display];
      [agentDisplay display];
      [worldRaster drawSelf];
    }
  return self;
}

- (char*)setParameterFile: (char*)aString
{
  char temp[100];
  sprintf (temp,"parameters/%s.scm",aString); 
  parameterFile = strdup (temp);
  return parameterFile;
}
      
- saveParameters: (char*)aString
 {
   char dataArchiveName[100];
   snprintf(dataArchiveName,100,"parameters/%s.scm",aString);
   id dataArchiver = [LispArchiver create: self setPath: dataArchiveName];
   
   [dataArchiver putShallow: "model" object: modelSwarm];
   [dataArchiver sync];
   [dataArchiver drop];
   return self;
 }

// The display schedule
- buildActions 
{
  [super buildActions];
  
  [modelSwarm buildActions];

  // The display schedule is just a list of actions in a row
  //   display the sugar
  //   display the agents
  //   update the display itself
  //   update the graphs
  //   update probes
  //   update control panel events
  displayActions = [ActionGroup create: self];
  [displayActions createActionTo: self message: M(_updateDisplay_)];

  if (drawPopulationGraph)
    [displayActions createActionTo: populationGraph message: M(step)];
  if (drawAttributeGraph)
    [displayActions createActionTo: attributeGraph message: M(step)];
  if (drawTagGraph)
    [displayActions createActionTo: tagGraph message: M(step)];
  if (drawPriceGraph)
    [displayActions createActionTo: priceGraph message: M(step)];
  if (drawTradesGraph)
    [displayActions createActionTo: tradesGraph message: M(step)];
  if (drawQuantGraph)
    [displayActions createActionTo: quantGraph message: M(step)];
  if (drawForesightGraph)
    [displayActions createActionTo: foresightGraph message: M(step)];
  if (drawSicknessGraph)
    [displayActions createActionTo: sicknessGraph message: M(step)];
  if (drawPriceStdDevGraph)
    [displayActions createActionTo: priceStdDevGraph message: M(step)];
  if (drawSugarHistogram)
    [displayActions createActionTo: self message: M(_updateSugarHistogram_)];
  if (drawSpiceHistogram)
    [displayActions createActionTo: self message: M(_updateSpiceHistogram_)];
  if (drawMrsHistogram)
    [displayActions createActionTo: self message: M(_updateMrsHistogram_)];
  if (drawAgeHistogram)
    [displayActions createActionTo: self message: M(_updateAgeHistogram_)];

  [displayActions createActionTo: probeDisplayManager message: M(update)];
  [displayActions createActionTo: actionCache message: M(doTkEvents)];
#ifdef MAKEMOVIE
  [displayActions createActionTo: self message: M(writeFrame)];
#endif
  
  displaySchedule = [Schedule createBegin: self];
  [displaySchedule setRepeatInterval: displayFrequency];
  displaySchedule = [displaySchedule createEnd];
  [displaySchedule at: 0 createAction: displayActions];

  return self;
}  

// Scheduling details. The model swarm is activated here
- (id <Activity>)activateIn: swarmContext
{
  [super activateIn: swarmContext];
  [modelSwarm activateIn: self];
  [displaySchedule activateIn: self];
  return [self getActivity];
}

#ifdef MAKEMOVIE
- writeFrame
{
  char filename[256];
  id aPixmap;
  sprintf(filename, "%04ld.ppm", getCurrentTime());
  aPixmap = [Pixmap createBegin: self];
  [aPixmap setWidget: worldRaster];
  [aPixmap setDecorationsFlag: NO];
  aPixmap = [aPixmap createEnd];
  [aPixmap save: filename];
  [aPixmap drop];
  return self;
}
#endif

@end
